/*
 * 86Box    A hypervisor and IBM PC system emulator that specializes in
 *          running old operating systems and software designed for IBM
 *          PC systems and compatibles from 1981 through fairly recent
 *          system designs based on the PCI bus.
 *
 *          This file is part of the 86Box distribution.
 *
 *          Implementation of the NCR 5380 series of SCSI Host Adapters
 *          made by NCR. These controllers were designed for the ISA bus.
 *
 *
 *
 * Authors: Sarah Walker, <https://pcem-emulator.co.uk/>
 *          TheCollector1995, <mariogplayer@gmail.com>
 *          Fred N. van Kempen, <decwiz@yahoo.com>
 *
 *          Copyright 2017-2019 Sarah Walker.
 *          Copyright 2017-2019 TheCollector1995.
 *          Copyright 2017-2019 Fred N. van Kempen.
 */
#include <inttypes.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <wchar.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/io.h>
#include <86box/timer.h>
#include <86box/dma.h>
#include <86box/pic.h>
#include <86box/mca.h>
#include <86box/mem.h>
#include <86box/rom.h>
#include <86box/device.h>
#include <86box/nvr.h>
#include <86box/plat.h>
#include <86box/scsi.h>
#include <86box/scsi_device.h>
#include <86box/scsi_ncr5380.h>

#define LCS6821N_ROM            "roms/scsi/ncr5380/Longshine LCS-6821N - BIOS version 1.04.bin"
#define RT1000B_810R_ROM        "roms/scsi/ncr5380/Rancho_RT1000_RTBios_version_8.10R.bin"
#define RT1000B_820R_ROM        "roms/scsi/ncr5380/RTBIOS82.ROM"
#define T130B_ROM               "roms/scsi/ncr5380/trantor_t130b_bios_v2.14.bin"
#define T128_ROM                "roms/scsi/ncr5380/trantor_t128_bios_v1.12.bin"
#define COREL_LS2000_ROM        "roms/scsi/ncr5380/Corel LS2000 - BIOS ROM - Ver 1.65.bin"

#define NCR_CURDATA             0 /* current SCSI data (read only) */
#define NCR_OUTDATA             0 /* output data (write only) */
#define NCR_INITCOMMAND         1 /* initiator command (read/write) */
#define NCR_MODE                2 /* mode (read/write) */
#define NCR_TARGETCMD           3 /* target command (read/write) */
#define NCR_SELENABLE           4 /* select enable (write only) */
#define NCR_BUSSTATUS           4 /* bus status (read only) */
#define NCR_STARTDMA            5 /* start DMA send (write only) */
#define NCR_BUSANDSTAT          5 /* bus and status (read only) */
#define NCR_DMATARGET           6 /* DMA target (write only) */
#define NCR_INPUTDATA           6 /* input data (read only) */
#define NCR_DMAINIRECV          7 /* DMA initiator receive (write only) */
#define NCR_RESETPARITY         7 /* reset parity/interrupt (read only) */

#define ICR_DBP                 0x01
#define ICR_ATN                 0x02
#define ICR_SEL                 0x04
#define ICR_BSY                 0x08
#define ICR_ACK                 0x10
#define ICR_ARB_LOST            0x20
#define ICR_ARB_IN_PROGRESS     0x40

#define MODE_ARBITRATE          0x01
#define MODE_DMA                0x02
#define MODE_MONITOR_BUSY       0x04
#define MODE_ENA_EOP_INT        0x08

#define STATUS_ACK              0x01
#define STATUS_BUSY_ERROR       0x04
#define STATUS_PHASE_MATCH      0x08
#define STATUS_INT              0x10
#define STATUS_DRQ              0x40
#define STATUS_END_OF_DMA       0x80

#define TCR_IO                  0x01
#define TCR_CD                  0x02
#define TCR_MSG                 0x04
#define TCR_REQ                 0x08
#define TCR_LAST_BYTE_SENT      0x80

#define CTRL_DATA_DIR           0x40
#define STATUS_BUFFER_NOT_READY 0x04
#define STATUS_53C80_ACCESSIBLE 0x80

typedef struct ncr_t {
    uint8_t icr;
    uint8_t mode;
    uint8_t tcr;
    uint8_t data_wait;
    uint8_t isr;
    uint8_t output_data;
    uint8_t target_id;
    uint8_t tx_data;
    uint8_t msglun;

    uint8_t command[20];
    uint8_t msgout[4];
    int     msgout_pos;
    int     is_msgout;

    int dma_mode;
    int cur_bus;
    int bus_in;
    int new_phase;
    int state;
    int clear_req;
    int wait_data;
    int wait_complete;
    int command_pos;
    int data_pos;
} ncr_t;

typedef struct t128_t {
    uint8_t ctrl;
    uint8_t status;
    uint8_t buffer[512];
    uint8_t ext_ram[0x80];
    uint8_t block_count;

    int block_loaded;
    int pos, host_pos;

    int bios_enabled;
} t128_t;

typedef struct ncr5380_t {
    ncr_t  ncr;
    t128_t t128;

    const char *name;

    uint8_t buffer[128];
    uint8_t int_ram[0x40];
    uint8_t ext_ram[0x600];

    uint32_t rom_addr;
    uint16_t base;

    int8_t  irq;
    int8_t  type;
    int8_t  bios_ver;
    uint8_t block_count;
    uint8_t status_ctrl;
    uint8_t bus, pad;

    rom_t         bios_rom;
    mem_mapping_t mapping;

    int block_count_loaded;

    int buffer_pos;
    int buffer_host_pos;

    int dma_enabled;

    pc_timer_t timer;
    double     period;

    int     ncr_busy;
    uint8_t pos_regs[8];
} ncr5380_t;

#define STATE_IDLE            0
#define STATE_COMMAND         1
#define STATE_DATAIN          2
#define STATE_DATAOUT         3
#define STATE_STATUS          4
#define STATE_MESSAGEIN       5
#define STATE_SELECT          6
#define STATE_MESSAGEOUT      7
#define STATE_MESSAGE_ID      8

#define DMA_IDLE              0
#define DMA_SEND              1
#define DMA_INITIATOR_RECEIVE 2

static int cmd_len[8] = { 6, 10, 10, 6, 16, 12, 10, 6 };

#ifdef ENABLE_NCR5380_LOG
int ncr5380_do_log = ENABLE_NCR5380_LOG;

static void
ncr_log(const char *fmt, ...)
{
    va_list ap;

    if (ncr5380_do_log) {
        va_start(ap, fmt);
        pclog_ex(fmt, ap);
        va_end(ap);
    }
}
#else
#    define ncr_log(fmt, ...)
#endif

#define SET_BUS_STATE(ncr, state) ncr->cur_bus = (ncr->cur_bus & ~(SCSI_PHASE_MESSAGE_IN)) | (state & (SCSI_PHASE_MESSAGE_IN))

static void
ncr_callback(void *priv);

static void
ncr_irq(ncr5380_t *ncr_dev, ncr_t *ncr, int set_irq)
{
    if (set_irq) {
        ncr->isr |= STATUS_INT;
        picint(1 << ncr_dev->irq);
    } else {
        ncr->isr &= ~STATUS_INT;
        picintc(1 << ncr_dev->irq);
    }
}

static int
get_dev_id(uint8_t data)
{
    for (uint8_t c = 0; c < SCSI_ID_MAX; c++) {
        if (data & (1 << c))
            return c;
    }

    return -1;
}

static int
getmsglen(uint8_t *msgp, int len)
{
    uint8_t msg = msgp[0];
    if (msg == 0 || (msg >= 0x02 && msg <= 0x1f) || msg >= 0x80)
        return 1;
    if (msg >= 0x20 && msg <= 0x2f)
        return 2;
    if (len < 2)
        return 3;
    return msgp[1];
}

static void
ncr_reset(ncr5380_t *ncr_dev, ncr_t *ncr)
{
    memset(ncr, 0x00, sizeof(ncr_t));
    ncr_log("NCR Reset\n");

    timer_stop(&ncr_dev->timer);

    for (int i = 0; i < 8; i++)
        scsi_device_reset(&scsi_devices[ncr_dev->bus][i]);

    ncr_irq(ncr_dev, ncr, 0);
}

static uint32_t
get_bus_host(ncr_t *ncr)
{
    uint32_t bus_host = 0;

    if (ncr->icr & ICR_DBP)
        bus_host |= BUS_DBP;

    if (ncr->icr & ICR_SEL)
        bus_host |= BUS_SEL;

    if (ncr->tcr & TCR_IO)
        bus_host |= BUS_IO;

    if (ncr->tcr & TCR_CD)
        bus_host |= BUS_CD;

    if (ncr->tcr & TCR_MSG)
        bus_host |= BUS_MSG;

    if (ncr->tcr & TCR_REQ)
        bus_host |= BUS_REQ;

    if (ncr->icr & ICR_BSY)
        bus_host |= BUS_BSY;

    if (ncr->icr & ICR_ATN)
        bus_host |= BUS_ATN;

    if (ncr->icr & ICR_ACK)
        bus_host |= BUS_ACK;

    if (ncr->mode & MODE_ARBITRATE)
        bus_host |= BUS_ARB;

    return (bus_host | BUS_SETDATA(ncr->output_data));
}

static void
ncr_bus_read(ncr5380_t *ncr_dev)
{
    ncr_t               *ncr = &ncr_dev->ncr;
    const scsi_device_t *dev;
    int                  phase;

    /*Wait processes to handle bus requests*/
    if (ncr->clear_req) {
        ncr->clear_req--;
        if (!ncr->clear_req) {
            ncr_log("Prelude to command data\n");
            SET_BUS_STATE(ncr, ncr->new_phase);
            ncr->cur_bus |= BUS_REQ;
        }
    }

    if (ncr->wait_data) {
        ncr->wait_data--;
        if (!ncr->wait_data) {
            dev = &scsi_devices[ncr_dev->bus][ncr->target_id];
            SET_BUS_STATE(ncr, ncr->new_phase);
            phase = (ncr->cur_bus & SCSI_PHASE_MESSAGE_IN);

            if (phase == SCSI_PHASE_DATA_IN) {
                ncr->tx_data = dev->sc->temp_buffer[ncr->data_pos++];
                ncr->state   = STATE_DATAIN;
                ncr->cur_bus = (ncr->cur_bus & ~BUS_DATAMASK) | BUS_SETDATA(ncr->tx_data) | BUS_DBP;
            } else if (phase == SCSI_PHASE_DATA_OUT) {
                if (ncr->new_phase & BUS_IDLE) {
                    ncr->state = STATE_IDLE;
                    ncr->cur_bus &= ~BUS_BSY;
                } else
                    ncr->state = STATE_DATAOUT;
            } else if (phase == SCSI_PHASE_STATUS) {
                ncr->cur_bus |= BUS_REQ;
                ncr->state   = STATE_STATUS;
                ncr->cur_bus = (ncr->cur_bus & ~BUS_DATAMASK) | BUS_SETDATA(dev->status) | BUS_DBP;
            } else if (phase == SCSI_PHASE_MESSAGE_IN) {
                ncr->state   = STATE_MESSAGEIN;
                ncr->cur_bus = (ncr->cur_bus & ~BUS_DATAMASK) | BUS_SETDATA(0) | BUS_DBP;
            } else if (phase == SCSI_PHASE_MESSAGE_OUT) {
                ncr->cur_bus |= BUS_REQ;
                ncr->state   = STATE_MESSAGEOUT;
                ncr->cur_bus = (ncr->cur_bus & ~BUS_DATAMASK) | BUS_SETDATA(ncr->target_id >> 5) | BUS_DBP;
            }
        }
    }

    if (ncr->wait_complete) {
        ncr->wait_complete--;
        if (!ncr->wait_complete)
            ncr->cur_bus |= BUS_REQ;
    }
}

static void
ncr_bus_update(void *priv, int bus)
{
    ncr5380_t     *ncr_dev = (ncr5380_t *) priv;
    ncr_t         *ncr     = &ncr_dev->ncr;
    scsi_device_t *dev     = &scsi_devices[ncr_dev->bus][ncr->target_id];
    double         p;
    uint8_t        sel_data;
    int            msglen;

    /*Start the SCSI command layer, which will also make the timings*/
    if (bus & BUS_ARB)
        ncr->state = STATE_IDLE;

    ncr_log("State = %i\n", ncr->state);

    switch (ncr->state) {
        case STATE_IDLE:
            ncr->clear_req = ncr->wait_data = ncr->wait_complete = 0;
            if ((bus & BUS_SEL) && !(bus & BUS_BSY)) {
                ncr_log("Selection phase\n");
                sel_data = BUS_GETDATA(bus);

                ncr->target_id = get_dev_id(sel_data);

                ncr_log("Select - target ID = %i\n", ncr->target_id);

                /*Once the device has been found and selected, mark it as busy*/
                if ((ncr->target_id != (uint8_t) -1) && scsi_device_present(&scsi_devices[ncr_dev->bus][ncr->target_id])) {
                    ncr->cur_bus |= BUS_BSY;
                    ncr->state = STATE_SELECT;
                } else {
                    ncr_log("Device not found at ID %i, Current Bus BSY=%02x\n", ncr->target_id, ncr->cur_bus);
                    ncr->cur_bus = 0;
                }
            }
            break;
        case STATE_SELECT:
            if (!(bus & BUS_SEL)) {
                if (!(bus & BUS_ATN)) {
                    if ((ncr->target_id != (uint8_t) -1) && scsi_device_present(&scsi_devices[ncr_dev->bus][ncr->target_id])) {
                        ncr_log("Device found at ID %i, Current Bus BSY=%02x\n", ncr->target_id, ncr->cur_bus);
                        ncr->state   = STATE_COMMAND;
                        ncr->cur_bus = BUS_BSY | BUS_REQ;
                        ncr_log("CurBus BSY|REQ=%02x\n", ncr->cur_bus);
                        ncr->command_pos = 0;
                        SET_BUS_STATE(ncr, SCSI_PHASE_COMMAND);
                    } else {
                        ncr->state   = STATE_IDLE;
                        ncr->cur_bus = 0;
                    }
                } else {
                    ncr_log("Set to SCSI Message Out\n");
                    ncr->new_phase  = SCSI_PHASE_MESSAGE_OUT;
                    ncr->wait_data  = 4;
                    ncr->msgout_pos = 0;
                    ncr->is_msgout  = 1;
                }
            }
            break;
        case STATE_COMMAND:
            if ((bus & BUS_ACK) && !(ncr->bus_in & BUS_ACK)) {
                /*Write command byte to the output data register*/
                ncr->command[ncr->command_pos++] = BUS_GETDATA(bus);
                ncr->clear_req                   = 3;
                ncr->new_phase                   = ncr->cur_bus & SCSI_PHASE_MESSAGE_IN;
                ncr->cur_bus &= ~BUS_REQ;

                ncr_log("Command pos=%i, output data=%02x\n", ncr->command_pos, BUS_GETDATA(bus));

                if (ncr->command_pos == cmd_len[(ncr->command[0] >> 5) & 7]) {
                    if (ncr->is_msgout) {
                        ncr->is_msgout = 0;
#if 0
                        ncr->command[1] = (ncr->command[1] & 0x1f) | (ncr->msglun << 5);
#endif
                    }

                    /*Reset data position to default*/
                    ncr->data_pos = 0;

                    dev = &scsi_devices[ncr_dev->bus][ncr->target_id];

                    ncr_log("SCSI Command 0x%02X for ID %d, status code=%02x\n", ncr->command[0], ncr->target_id, dev->status);
                    dev->buffer_length = -1;
                    scsi_device_command_phase0(dev, ncr->command);
                    ncr_log("SCSI ID %i: Command %02X: Buffer Length %i, SCSI Phase %02X\n", ncr->target_id, ncr->command[0], dev->buffer_length, dev->phase);

                    ncr_dev->period = 1.0;
                    ncr->wait_data  = 4;
                    ncr->data_wait  = 0;

                    if (dev->status == SCSI_STATUS_OK) {
                        /*If the SCSI phase is Data In or Data Out, allocate the SCSI buffer based on the transfer length of the command*/
                        if (dev->buffer_length && (dev->phase == SCSI_PHASE_DATA_IN || dev->phase == SCSI_PHASE_DATA_OUT)) {
                            p = scsi_device_get_callback(dev);
                            ncr_dev->period = (p > 0.0) ? p : (((double) dev->buffer_length) * 0.2);
                            ncr_log("SCSI ID %i: command 0x%02x for p = %lf, update = %lf, len = %i, dmamode = %x\n", ncr->target_id, ncr->command[0], scsi_device_get_callback(dev), ncr_dev->period, dev->buffer_length, ncr->dma_mode);
                        }
                    }
                    ncr->new_phase = dev->phase;
                }
            }
            break;
        case STATE_DATAIN:
            dev = &scsi_devices[ncr_dev->bus][ncr->target_id];
            if ((bus & BUS_ACK) && !(ncr->bus_in & BUS_ACK)) {
                if (ncr->data_pos >= dev->buffer_length) {
                    ncr->cur_bus &= ~BUS_REQ;
                    scsi_device_command_phase1(dev);
                    ncr->new_phase     = SCSI_PHASE_STATUS;
                    ncr->wait_data     = 4;
                    ncr->wait_complete = 8;
                } else {
                    ncr->tx_data = dev->sc->temp_buffer[ncr->data_pos++];
                    ncr->cur_bus = (ncr->cur_bus & ~BUS_DATAMASK) | BUS_SETDATA(ncr->tx_data) | BUS_DBP | BUS_REQ;
                    if (ncr->dma_mode == DMA_IDLE) { /*If a data in command that is not read 6/10 has been issued*/
                        ncr->data_wait |= 1;
                        ncr_log("DMA mode idle in\n");
                        timer_on_auto(&ncr_dev->timer, ncr_dev->period);
                    } else {
                        ncr_log("DMA mode IN.\n");
                        ncr->clear_req = 3;
                    }

                    ncr->cur_bus &= ~BUS_REQ;
                    ncr->new_phase = SCSI_PHASE_DATA_IN;
                }
            }
            break;
        case STATE_DATAOUT:
            dev = &scsi_devices[ncr_dev->bus][ncr->target_id];
            if ((bus & BUS_ACK) && !(ncr->bus_in & BUS_ACK)) {
                dev->sc->temp_buffer[ncr->data_pos++] = BUS_GETDATA(bus);

                if (ncr->data_pos >= dev->buffer_length) {
                    ncr->cur_bus &= ~BUS_REQ;
                    scsi_device_command_phase1(dev);
                    ncr->new_phase     = SCSI_PHASE_STATUS;
                    ncr->wait_data     = 4;
                    ncr->wait_complete = 8;
                } else {
                    /*More data is to be transferred, place a request*/
                    if (ncr->dma_mode == DMA_IDLE) { /*If a data out command that is not write 6/10 has been issued*/
                        ncr->data_wait |= 1;
                        ncr_log("DMA mode idle out\n");
                        timer_on_auto(&ncr_dev->timer, ncr_dev->period);
                    } else
                        ncr->clear_req = 3;

                    ncr->cur_bus &= ~BUS_REQ;
                    ncr_log("CurBus ~REQ_DataOut=%02x\n", ncr->cur_bus);
                }
            }
            break;
        case STATE_STATUS:
            if ((bus & BUS_ACK) && !(ncr->bus_in & BUS_ACK)) {
                /*All transfers done, wait until next transfer*/
                scsi_device_identify(&scsi_devices[ncr_dev->bus][ncr->target_id], SCSI_LUN_USE_CDB);
                ncr->cur_bus &= ~BUS_REQ;
                ncr->new_phase     = SCSI_PHASE_MESSAGE_IN;
                ncr->wait_data     = 4;
                ncr->wait_complete = 8;
            }
            break;
        case STATE_MESSAGEIN:
            if ((bus & BUS_ACK) && !(ncr->bus_in & BUS_ACK)) {
                ncr->cur_bus &= ~BUS_REQ;
                ncr->new_phase = BUS_IDLE;
                ncr->wait_data = 4;
            }
            break;
        case STATE_MESSAGEOUT:
            ncr_log("Ack on MSGOUT = %02x\n", (bus & BUS_ACK));
            if ((bus & BUS_ACK) && !(ncr->bus_in & BUS_ACK)) {
                ncr->msgout[ncr->msgout_pos++] = BUS_GETDATA(bus);
                msglen                         = getmsglen(ncr->msgout, ncr->msgout_pos);
                if (ncr->msgout_pos >= msglen) {
                    if ((ncr->msgout[0] & (0x80 | 0x20)) == 0x80)
                        ncr->msglun = ncr->msgout[0] & 7;
                    ncr->cur_bus &= ~BUS_REQ;
                    ncr->state = STATE_MESSAGE_ID;
                }
            }
            break;
        case STATE_MESSAGE_ID:
            if ((ncr->target_id != (uint8_t) -1) && scsi_device_present(&scsi_devices[ncr_dev->bus][ncr->target_id])) {
                ncr_log("Device found at ID %i on MSGOUT, Current Bus BSY=%02x\n", ncr->target_id, ncr->cur_bus);
                scsi_device_identify(&scsi_devices[ncr_dev->bus][ncr->target_id], ncr->msglun);
                ncr->state   = STATE_COMMAND;
                ncr->cur_bus = BUS_BSY | BUS_REQ;
                ncr_log("CurBus BSY|REQ=%02x\n", ncr->cur_bus);
                ncr->command_pos = 0;
                SET_BUS_STATE(ncr, SCSI_PHASE_COMMAND);
            }
            break;

        default:
            break;
    }

    ncr->bus_in = bus;
}

static void
ncr_write(uint16_t port, uint8_t val, void *priv)
{
    ncr5380_t           *ncr_dev  = (ncr5380_t *) priv;
    ncr_t               *ncr      = &ncr_dev->ncr;
    scsi_device_t       *dev      = &scsi_devices[ncr_dev->bus][ncr->target_id];
    int                  bus_host = 0;

    ncr_log("NCR5380 write(%04x,%02x)\n", port & 7, val);

    switch (port & 7) {
        case 0: /* Output data register */
            ncr_log("Write: Output data register, val = %02x\n", val);
            ncr->output_data = val;
            break;

        case 1: /* Initiator Command Register */
            ncr_log("Write: Initiator command register\n");
            if ((val & 0x80) && !(ncr->icr & 0x80)) {
                ncr_log("Resetting the 5380\n");
                ncr_reset(ncr_dev, &ncr_dev->ncr);
            }
            ncr->icr = val;
            break;

        case 2: /* Mode register */
            ncr_log("Write: Mode register, val=%02x.\n", val);
            if ((val & MODE_ARBITRATE) && !(ncr->mode & MODE_ARBITRATE)) {
                ncr->icr &= ~ICR_ARB_LOST;
                ncr->icr |= ICR_ARB_IN_PROGRESS;
            }

            ncr->mode = val;

            if (ncr_dev->type == 3) {
                /*Don't stop the timer until it finishes the transfer*/
                if (ncr_dev->t128.block_loaded && (ncr->mode & MODE_DMA)) {
                    ncr_log("Continuing DMA mode\n");
                    timer_on_auto(&ncr_dev->timer, ncr_dev->period + 1.0);
                }

                /*When a pseudo-DMA transfer has completed (Send or Initiator Receive), mark it as complete and idle the status*/
                if (!ncr_dev->t128.block_loaded && !(ncr->mode & MODE_DMA)) {
                    ncr_log("No DMA mode\n");
                    ncr->tcr &= ~TCR_LAST_BYTE_SENT;
                    ncr->isr &= ~STATUS_END_OF_DMA;
                    ncr->dma_mode = DMA_IDLE;
                }
            } else {
                /*When a pseudo-DMA transfer has completed (Send or Initiator Receive), mark it as complete and idle the status*/
                if (!ncr_dev->block_count_loaded && !(ncr->mode & MODE_DMA)) {
                    ncr_log("No DMA mode\n");
                    ncr->tcr &= ~TCR_LAST_BYTE_SENT;
                    ncr->isr &= ~STATUS_END_OF_DMA;
                    ncr->dma_mode = DMA_IDLE;
                }
            }
            break;

        case 3: /* Target Command Register */
            ncr_log("Write: Target Command register\n");
            ncr->tcr = val;
            break;

        case 4: /* Select Enable Register */
            ncr_log("Write: Select Enable register\n");
            break;

        case 5: /* start DMA Send */
            ncr_log("Write: start DMA send register\n");
            /*a Write 6/10 has occurred, start the timer when the block count is loaded*/
            ncr->dma_mode = DMA_SEND;
            if (ncr_dev->type == 3) {
                if ((ncr->mode & MODE_DMA) && !timer_is_on(&ncr_dev->timer) && (dev->buffer_length > 0)) {
                    memset(ncr_dev->t128.buffer, 0, MIN(512, dev->buffer_length));
                    ncr_dev->t128.status |= 0x04;
                    ncr_dev->t128.host_pos = 0;
                    ncr_dev->t128.block_count = dev->buffer_length >> 9;

                    if (dev->buffer_length < 512)
                        ncr_dev->t128.block_count = 1;

                    ncr_dev->t128.block_loaded = 1;
                }
            }
            break;

        case 7: /* start DMA Initiator Receive */
            ncr_log("Write: start DMA initiator receive register, dma? = %02x\n", ncr->mode & MODE_DMA);
            /*a Read 6/10 has occurred, start the timer when the block count is loaded*/
            ncr->dma_mode = DMA_INITIATOR_RECEIVE;
            if (ncr_dev->type == 3) {
                if ((ncr->mode & MODE_DMA) && !timer_is_on(&ncr_dev->timer) && (dev->buffer_length > 0)) {
                    memset(ncr_dev->t128.buffer, 0, MIN(512, dev->buffer_length));
                    ncr_dev->t128.status |= 0x04;
                    ncr_dev->t128.host_pos = MIN(512, dev->buffer_length);
                    ncr_dev->t128.block_count = dev->buffer_length >> 9;

                    if (dev->buffer_length < 512)
                        ncr_dev->t128.block_count = 1;

                    ncr_dev->t128.block_loaded = 1;
                    timer_on_auto(&ncr_dev->timer, 0.02);
                }
            }
            break;

        default:
            ncr_log("NCR5380: bad write %04x %02x\n", port, val);
            break;
    }

    bus_host = get_bus_host(ncr);
    ncr_bus_update(priv, bus_host);
}

static uint8_t
ncr_read(uint16_t port, void *priv)
{
    ncr5380_t *ncr_dev = (ncr5380_t *) priv;
    ncr_t     *ncr     = &ncr_dev->ncr;
    uint8_t    ret     = 0xff;
    int        bus;
    int        bus_state;

    switch (port & 7) {
        case 0: /* Current SCSI data */
            ncr_log("Read: Current SCSI data register\n");
            if (ncr->icr & ICR_DBP) {
                /*Return the data from the output register if on data bus phase from ICR*/
                ncr_log("Data Bus Phase, ret = %02x\n", ncr->output_data);
                ret = ncr->output_data;
            } else {
                /*Return the data from the SCSI bus*/
                ncr_bus_read(ncr_dev);
                ncr_log("NCR GetData=%02x\n", BUS_GETDATA(ncr->cur_bus));
                ret = BUS_GETDATA(ncr->cur_bus);
            }
            break;

        case 1: /* Initiator Command Register */
            ncr_log("Read: Initiator Command register, NCR ICR Read=%02x\n", ncr->icr);
            ret = ncr->icr;
            break;

        case 2: /* Mode register */
            ncr_log("Read: Mode register = %02x.\n", ncr->mode);
            ret = ncr->mode;
            break;

        case 3: /* Target Command Register */
            ncr_log("Read: Target Command register, NCR target stat=%02x\n", ncr->tcr);
            ret = ncr->tcr;
            break;

        case 4: /* Current SCSI Bus status */
            ncr_log("Read: SCSI bus status register\n");
            ret = 0;
            ncr_bus_read(ncr_dev);
            ncr_log("NCR cur bus stat=%02x\n", ncr->cur_bus & 0xff);
            ret |= (ncr->cur_bus & 0xff);
            if (ncr->icr & ICR_SEL)
                ret |= BUS_SEL;
            if (ncr->icr & ICR_BSY)
                ret |= BUS_BSY;
            break;

        case 5: /* Bus and Status register */
            ncr_log("Read: Bus and Status register\n");
            ret = 0;

            bus = get_bus_host(ncr);
            ncr_log("Get host from Interrupt\n");

            /*Check if the phase in process matches with TCR's*/
            if ((bus & SCSI_PHASE_MESSAGE_IN) == (ncr->cur_bus & SCSI_PHASE_MESSAGE_IN)) {
                ncr_log("Phase match\n");
                ret |= STATUS_PHASE_MATCH;
            }

            ncr_bus_read(ncr_dev);
            bus = ncr->cur_bus;

            if ((bus & BUS_ACK) || (ncr->icr & ICR_ACK))
                ret |= STATUS_ACK;
            if ((bus & BUS_ATN) || (ncr->icr & ICR_ATN))
                ret |= 0x02;

            if ((bus & BUS_REQ) && (ncr->mode & MODE_DMA)) {
                ncr_log("Entering DMA mode\n");
                ret |= STATUS_DRQ;

                bus_state = 0;

                if (bus & BUS_IO)
                    bus_state |= TCR_IO;
                if (bus & BUS_CD)
                    bus_state |= TCR_CD;
                if (bus & BUS_MSG)
                    bus_state |= TCR_MSG;
                if ((ncr->tcr & 7) != bus_state) {
                    ncr_irq(ncr_dev, ncr, 1);
                    ncr_log("IRQ issued\n");
                }
            }
            if (!(bus & BUS_BSY) && (ncr->mode & MODE_MONITOR_BUSY)) {
                ncr_log("Busy error\n");
                ret |= STATUS_BUSY_ERROR;
            }
            ret |= (ncr->isr & (STATUS_INT | STATUS_END_OF_DMA));
            break;

        case 6:
            ret = ncr->tx_data;
            break;

        case 7: /* reset Parity/Interrupt */
            ncr->isr &= ~(STATUS_BUSY_ERROR | 0x20);
            ncr_irq(ncr_dev, ncr, 0);
            ncr_log("Reset Interrupt\n");
            break;

        default:
            ncr_log("NCR5380: bad read %04x\n", port);
            break;
    }

    ncr_log("NCR5380 read(%04x)=%02x\n", port & 7, ret);

    return ret;
}

/* Memory-mapped I/O READ handler. */
static uint8_t
memio_read(uint32_t addr, void *priv)
{
    ncr5380_t           *ncr_dev = (ncr5380_t *) priv;
    ncr_t               *ncr     = &ncr_dev->ncr;
    scsi_device_t       *dev     = &scsi_devices[ncr_dev->bus][ncr->target_id];
    uint8_t              ret     = 0xff;

    addr &= 0x3fff;

    if (addr < 0x2000)
        ret = ncr_dev->bios_rom.rom[addr & 0x1fff];
    else if (addr < 0x3800)
        ret = 0xff;
    else if (addr >= 0x3a00)
        ret = ncr_dev->ext_ram[addr - 0x3a00];
    else
        switch (addr & 0x3f80) {
            case 0x3800:
#if ENABLE_NCR5380_LOG
                ncr_log("Read intRAM %02x %02x\n", addr & 0x3f, ncr_dev->int_ram[addr & 0x3f]);
#endif
                ret = ncr_dev->int_ram[addr & 0x3f];
                break;

            case 0x3880:
#if ENABLE_NCR5380_LOG
                ncr_log("Read 53c80 %04x\n", addr);
#endif
                ret = ncr_read(addr, ncr_dev);
                break;

            case 0x3900:
                if (ncr_dev->buffer_host_pos >= MIN(128, dev->buffer_length) || (!(ncr_dev->status_ctrl & CTRL_DATA_DIR))) {
                    ret = 0xff;
                    ncr_log("No Read.\n");
                } else {
                    ret = ncr_dev->buffer[ncr_dev->buffer_host_pos++];
                    ncr_log("Read host pos = %i, ret = %02x\n", ncr_dev->buffer_host_pos, ret);

                    if (ncr_dev->buffer_host_pos == MIN(128, dev->buffer_length)) {
                        ncr_dev->status_ctrl |= STATUS_BUFFER_NOT_READY;
                        ncr_log("Transfer busy read, status = %02x\n", ncr_dev->status_ctrl);
                    }
                }
                break;

            case 0x3980:
                switch (addr) {
                    case 0x3980: /* status */
                        ret = ncr_dev->status_ctrl;
                        ncr_log("NCR status ctrl read=%02x\n", ncr_dev->status_ctrl & STATUS_BUFFER_NOT_READY);
                        if (!ncr_dev->ncr_busy)
                            ret |= STATUS_53C80_ACCESSIBLE;
                        if (ncr->mode & 0x30) {            /*Parity bits*/
                            if (!(ncr->mode & MODE_DMA)) { /*This is to avoid RTBios 8.10R BIOS problems with the hard disk and detection.*/
                                ret |= 0x01;               /*If the parity bits are set, bit 0 of the 53c400 status port should be set as well.*/
                                ncr->mode = 0;             /*Required by RTASPI10.SYS otherwise it won't initialize.*/
                            }
                        }
                        ncr_log("NCR 53c400 status = %02x.\n", ret);
                        break;

                    case 0x3981: /* block counter register*/
                        ret = ncr_dev->block_count;
                        break;

                    case 0x3982: /* switch register read */
                        ret = 0xf8;
                        ret |= (ncr_dev->irq & 0x07);
                        ncr_log("Switches read=%02x.\n", ret);
                        break;

                    default:
                        break;
                }
                break;

            default:
                break;
        }

#if ENABLE_NCR5380_LOG
    if (addr >= 0x3880)
        ncr_log("memio_read(%08x)=%02x\n", addr, ret);
#endif

    return ret;
}

/* Memory-mapped I/O WRITE handler. */
static void
memio_write(uint32_t addr, uint8_t val, void *priv)
{
    ncr5380_t           *ncr_dev = (ncr5380_t *) priv;
    ncr_t               *ncr     = &ncr_dev->ncr;
    scsi_device_t       *dev     = &scsi_devices[ncr_dev->bus][ncr->target_id];

    addr &= 0x3fff;

    if (addr >= 0x3a00)
        ncr_dev->ext_ram[addr - 0x3a00] = val;
    else
        switch (addr & 0x3f80) {
            case 0x3800:
                ncr_dev->int_ram[addr & 0x3f] = val;
                break;

            case 0x3880:
                ncr_write(addr, val, ncr_dev);
                break;

            case 0x3900:
                if (!(ncr_dev->status_ctrl & CTRL_DATA_DIR) && ncr_dev->buffer_host_pos < MIN(128, dev->buffer_length)) {
                    ncr_dev->buffer[ncr_dev->buffer_host_pos++] = val;

                    ncr_log("Write host pos = %i, val = %02x\n", ncr_dev->buffer_host_pos, val);

                    if (ncr_dev->buffer_host_pos == MIN(128, dev->buffer_length)) {
                        ncr_dev->status_ctrl |= STATUS_BUFFER_NOT_READY;
                        ncr_dev->ncr_busy = 1;
                    }
                }
                break;

            case 0x3980:
                switch (addr) {
                    case 0x3980: /* Control */
                        ncr_log("NCR 53c400 control = %02x, mode = %02x.\n", val, ncr->mode);
                        if ((val & CTRL_DATA_DIR) && !(ncr_dev->status_ctrl & CTRL_DATA_DIR)) {
                            ncr_dev->buffer_host_pos = MIN(128, dev->buffer_length);
                            ncr_dev->status_ctrl |= STATUS_BUFFER_NOT_READY;
                        } else if (!(val & CTRL_DATA_DIR) && (ncr_dev->status_ctrl & CTRL_DATA_DIR)) {
                            ncr_dev->buffer_host_pos = 0;
                            ncr_dev->status_ctrl &= ~STATUS_BUFFER_NOT_READY;
                        }
                        ncr_dev->status_ctrl = (ncr_dev->status_ctrl & 0x87) | (val & 0x78);
                        break;

                    case 0x3981: /* block counter register */
                        ncr_log("Write block counter register: val=%d, dma mode=%x, period=%lf\n", val, ncr->dma_mode, ncr_dev->period);
                        ncr_dev->block_count        = val;
                        ncr_dev->block_count_loaded = 1;

                        if (ncr_dev->status_ctrl & CTRL_DATA_DIR) {
                            ncr_dev->buffer_host_pos = MIN(128, dev->buffer_length);
                            ncr_dev->status_ctrl |= STATUS_BUFFER_NOT_READY;
                        } else {
                            ncr_dev->buffer_host_pos = 0;
                            ncr_dev->status_ctrl &= ~STATUS_BUFFER_NOT_READY;
                        }
                        if ((ncr->mode & MODE_DMA) && !timer_is_on(&ncr_dev->timer) && (dev->buffer_length > 0)) {
                            memset(ncr_dev->buffer, 0, MIN(128, dev->buffer_length));
                            ncr_log("DMA timer on\n");
                            timer_on_auto(&ncr_dev->timer, ncr_dev->period);
                        }
                        break;

                    default:
                        break;
                }
                break;

            default:
                break;
        }
}

/* Memory-mapped I/O READ handler for the Trantor T130B. */
static uint8_t
t130b_read(uint32_t addr, void *priv)
{
    const ncr5380_t *ncr_dev = (ncr5380_t *) priv;
    uint8_t          ret     = 0xff;

    addr &= 0x3fff;
    if (addr < 0x1800)
        ret = ncr_dev->bios_rom.rom[addr & 0x1fff];
    else if (addr >= 0x1800 && addr < 0x1880)
        ret = ncr_dev->ext_ram[addr & 0x7f];

    ncr_log("MEM: Reading %02X from %08X\n", ret, addr);
    return ret;
}

/* Memory-mapped I/O WRITE handler for the Trantor T130B. */
static void
t130b_write(uint32_t addr, uint8_t val, void *priv)
{
    ncr5380_t *ncr_dev = (ncr5380_t *) priv;

    addr &= 0x3fff;
    ncr_log("MEM: Writing %02X to %08X\n", val, addr);
    if (addr >= 0x1800 && addr < 0x1880)
        ncr_dev->ext_ram[addr & 0x7f] = val;
}

static uint8_t
t130b_in(uint16_t port, void *priv)
{
    ncr5380_t *ncr_dev = (ncr5380_t *) priv;
    uint8_t    ret     = 0xff;

    switch (port & 0x0f) {
        case 0x00:
        case 0x01:
        case 0x02:
        case 0x03:
            ret = memio_read((port & 7) | 0x3980, ncr_dev);
            break;

        case 0x04:
        case 0x05:
            ret = memio_read(0x3900, ncr_dev);
            break;

        case 0x08:
        case 0x09:
        case 0x0a:
        case 0x0b:
        case 0x0c:
        case 0x0d:
        case 0x0e:
        case 0x0f:
            ret = ncr_read(port, ncr_dev);
            break;

        default:
            break;
    }

    ncr_log("I/O: Reading %02X from %04X\n", ret, port);
    return ret;
}

static void
t130b_out(uint16_t port, uint8_t val, void *priv)
{
    ncr5380_t *ncr_dev = (ncr5380_t *) priv;

    ncr_log("I/O: Writing %02X to %04X\n", val, port);

    switch (port & 0x0f) {
        case 0x00:
        case 0x01:
        case 0x02:
        case 0x03:
            memio_write((port & 7) | 0x3980, val, ncr_dev);
            break;

        case 0x04:
        case 0x05:
            memio_write(0x3900, val, ncr_dev);
            break;

        case 0x08:
        case 0x09:
        case 0x0a:
        case 0x0b:
        case 0x0c:
        case 0x0d:
        case 0x0e:
        case 0x0f:
            ncr_write(port, val, ncr_dev);
            break;

        default:
            break;
    }
}

static void
ncr_callback(void *priv)
{
    ncr5380_t     *ncr_dev = (ncr5380_t *) priv;
    ncr_t         *ncr     = &ncr_dev->ncr;
    scsi_device_t *dev     = &scsi_devices[ncr_dev->bus][ncr->target_id];
    int            bus;
    int            bytes_tx = 0;
    int            limit = 100;
    uint8_t        temp;

    if (ncr_dev->type != 3) {
        if (ncr->dma_mode != DMA_IDLE)
            timer_on_auto(&ncr_dev->timer, 1.0);
    } else {
        if ((ncr->dma_mode != DMA_IDLE) && (ncr->mode & MODE_DMA) && ncr_dev->t128.block_loaded) {
            if ((ncr_dev->t128.host_pos == MIN(512, dev->buffer_length)) && ncr_dev->t128.block_count)
                ncr_dev->t128.status |= 0x04;

            timer_on_auto(&ncr_dev->timer, ncr_dev->period / 55.0);
        }
    }

    if (ncr->data_wait & 1) {
        ncr->clear_req = 3;
        ncr->data_wait &= ~1;
        if (ncr_dev->type == 3) {
            if (ncr->dma_mode == DMA_IDLE)
                return;
        }
    }

    if (ncr_dev->type != 3) {
        if (ncr->dma_mode == DMA_IDLE)
            return;
    }

    switch (ncr->dma_mode) {
        case DMA_SEND:
            if (ncr_dev->type != 3) {
                if (ncr_dev->status_ctrl & CTRL_DATA_DIR) {
                    ncr_log("DMA_SEND with DMA direction set wrong\n");
                    break;
                }

                if (!(ncr_dev->status_ctrl & STATUS_BUFFER_NOT_READY)) {
                    ncr_log("Write buffer status ready\n");
                    break;
                }

                if (!ncr_dev->block_count_loaded)
                    break;

                while (bytes_tx < limit) {
                    for (uint8_t c = 0; c < 10; c++) {
                        ncr_bus_read(ncr_dev);
                        if (ncr->cur_bus & BUS_REQ)
                            break;
                    }

                    /* Data ready. */
                    temp = ncr_dev->buffer[ncr_dev->buffer_pos];

                    bus = get_bus_host(ncr) & ~BUS_DATAMASK;
                    bus |= BUS_SETDATA(temp);

                    ncr_bus_update(ncr_dev, bus | BUS_ACK);
                    ncr_bus_update(ncr_dev, bus & ~BUS_ACK);

                    ncr_dev->buffer_pos++;
                    bytes_tx++;
                    ncr_log("Buffer pos for writing = %d\n", ncr_dev->buffer_pos);

                    if (ncr_dev->buffer_pos == MIN(128, dev->buffer_length)) {
                        bytes_tx = 0;
                        ncr_dev->status_ctrl &= ~STATUS_BUFFER_NOT_READY;
                        ncr_dev->buffer_pos      = 0;
                        ncr_dev->buffer_host_pos = 0;
                        ncr_dev->ncr_busy    = 0;
                        ncr_dev->block_count = (ncr_dev->block_count - 1) & 0xff;
                        ncr_log("Remaining blocks to be written=%d\n", ncr_dev->block_count);
                        if (!ncr_dev->block_count) {
                            ncr_dev->block_count_loaded = 0;
                            ncr_log("IO End of write transfer\n");
                            ncr->tcr |= TCR_LAST_BYTE_SENT;
                            ncr->isr |= STATUS_END_OF_DMA;
                            timer_stop(&ncr_dev->timer);
                            if (ncr->mode & MODE_ENA_EOP_INT) {
                                ncr_log("NCR write irq\n");
                                ncr_irq(ncr_dev, ncr, 1);
                            }
                        }
                        break;
                    }
                }
            } else {
                if (!(ncr_dev->t128.status & 0x04)) {
                    ncr_log("Write status busy, block count = %i, host pos = %i\n", ncr_dev->t128.block_count, ncr_dev->t128.host_pos);
                    break;
                }

                if (!ncr_dev->t128.block_loaded) {
                    ncr_log("Write block not loaded\n");
                    break;
                }

                if (ncr_dev->t128.host_pos < MIN(512, dev->buffer_length))
                    break;

write_again:
                for (uint8_t c = 0; c < 10; c++) {
                    ncr_bus_read(ncr_dev);
                    if (ncr->cur_bus & BUS_REQ)
                        break;
                }

                /* Data ready. */
                temp = ncr_dev->t128.buffer[ncr_dev->t128.pos];

                bus = get_bus_host(ncr) & ~BUS_DATAMASK;
                bus |= BUS_SETDATA(temp);

                ncr_bus_update(ncr_dev, bus | BUS_ACK);
                ncr_bus_update(ncr_dev, bus & ~BUS_ACK);

                ncr_dev->t128.pos++;
                ncr_log("Buffer pos for writing = %d\n", ncr_dev->t128.pos);

                if (ncr_dev->t128.pos == MIN(512, dev->buffer_length)) {
                    ncr_dev->t128.pos = 0;
                    ncr_dev->t128.host_pos = 0;
                    ncr_dev->t128.status &= ~0x02;
                    ncr_dev->ncr_busy = 0;
                    ncr_dev->t128.block_count = (ncr_dev->t128.block_count - 1) & 0xff;
                    ncr_log("Remaining blocks to be written=%d\n", ncr_dev->t128.block_count);
                    if (!ncr_dev->t128.block_count) {
                        ncr_dev->t128.block_loaded = 0;
                        ncr_log("IO End of write transfer\n");
                        ncr->tcr |= TCR_LAST_BYTE_SENT;
                        ncr->isr |= STATUS_END_OF_DMA;
                        timer_stop(&ncr_dev->timer);
                        if (ncr->mode & MODE_ENA_EOP_INT) {
                            ncr_log("NCR write irq\n");
                            ncr_irq(ncr_dev, ncr, 1);
                        }
                    }
                    break;
                } else
                    goto write_again;
            }
            break;

        case DMA_INITIATOR_RECEIVE:
            if (ncr_dev->type != 3) {
                if (!(ncr_dev->status_ctrl & CTRL_DATA_DIR)) {
                    ncr_log("DMA_INITIATOR_RECEIVE with DMA direction set wrong\n");
                    break;
                }

                if (!(ncr_dev->status_ctrl & STATUS_BUFFER_NOT_READY)) {
                    ncr_log("Read buffer status ready\n");
                    break;
                }

                if (!ncr_dev->block_count_loaded)
                    break;

                while (bytes_tx < limit) {
                    for (uint8_t c = 0; c < 10; c++) {
                        ncr_bus_read(ncr_dev);
                        if (ncr->cur_bus & BUS_REQ)
                            break;
                    }

                    /* Data ready. */
                    ncr_bus_read(ncr_dev);
                    temp = BUS_GETDATA(ncr->cur_bus);

                    bus = get_bus_host(ncr);

                    ncr_bus_update(ncr_dev, bus | BUS_ACK);
                    ncr_bus_update(ncr_dev, bus & ~BUS_ACK);

                    ncr_dev->buffer[ncr_dev->buffer_pos++] = temp;
                    ncr_log("Buffer pos for reading = %d\n", ncr_dev->buffer_pos);
                    bytes_tx++;

                    if (ncr_dev->buffer_pos == MIN(128, dev->buffer_length)) {
                        bytes_tx = 0;
                        ncr_dev->status_ctrl &= ~STATUS_BUFFER_NOT_READY;
                        ncr_dev->buffer_pos      = 0;
                        ncr_dev->buffer_host_pos = 0;
                        ncr_dev->block_count = (ncr_dev->block_count - 1) & 0xff;
                        ncr_log("Remaining blocks to be read=%d\n", ncr_dev->block_count);
                        if (!ncr_dev->block_count) {
                            ncr_dev->block_count_loaded = 0;
                            ncr_log("IO End of read transfer\n");
                            ncr->isr |= STATUS_END_OF_DMA;
                            timer_stop(&ncr_dev->timer);
                            if (ncr->mode & MODE_ENA_EOP_INT) {
                                ncr_log("NCR read irq\n");
                                ncr_irq(ncr_dev, ncr, 1);
                            }
                        }
                        break;
                    }
                }
            } else {
                if (!(ncr_dev->t128.status & 0x04)) {
                    ncr_log("Read status busy, block count = %i, host pos = %i\n", ncr_dev->t128.block_count, ncr_dev->t128.host_pos);
                    break;
                }

                if (!ncr_dev->t128.block_loaded) {
                    ncr_log("Read block not loaded\n");
                    break;
                }

                if (ncr_dev->t128.host_pos < MIN(512, dev->buffer_length))
                    break;

read_again:
                for (uint8_t c = 0; c < 10; c++) {
                    ncr_bus_read(ncr_dev);
                    if (ncr->cur_bus & BUS_REQ)
                        break;
                }

                /* Data ready. */
                ncr_bus_read(ncr_dev);
                temp = BUS_GETDATA(ncr->cur_bus);

                bus = get_bus_host(ncr);

                ncr_bus_update(ncr_dev, bus | BUS_ACK);
                ncr_bus_update(ncr_dev, bus & ~BUS_ACK);

                ncr_dev->t128.buffer[ncr_dev->t128.pos++] = temp;
                ncr_log("Buffer pos for reading=%d, temp=%02x, len=%d.\n", ncr_dev->t128.pos, temp, dev->buffer_length);

                if (ncr_dev->t128.pos == MIN(512, dev->buffer_length)) {
                    ncr_dev->t128.pos      = 0;
                    ncr_dev->t128.host_pos = 0;
                    ncr_dev->t128.status &= ~0x02;
                    ncr_dev->t128.block_count = (ncr_dev->t128.block_count - 1) & 0xff;
                    ncr_log("Remaining blocks to be read=%d, status=%02x, len=%i, cdb[0] = %02x\n", ncr_dev->t128.block_count, ncr_dev->t128.status, dev->buffer_length, ncr->command[0]);
                    if (!ncr_dev->t128.block_count) {
                        ncr_dev->t128.block_loaded = 0;
                        ncr_log("IO End of read transfer\n");
                        ncr->isr |= STATUS_END_OF_DMA;
                        timer_stop(&ncr_dev->timer);
                        if (ncr->mode & MODE_ENA_EOP_INT) {
                            ncr_log("NCR read irq\n");
                            ncr_irq(ncr_dev, ncr, 1);
                        }
                    }
                    break;
                } else
                    goto read_again;
            }
            break;

        default:
            break;
    }

    ncr_bus_read(ncr_dev);

    if (!(ncr->cur_bus & BUS_BSY) && (ncr->mode & MODE_MONITOR_BUSY)) {
        ncr_log("Updating DMA\n");
        ncr->mode &= ~MODE_DMA;
        ncr->dma_mode = DMA_IDLE;
        if (ncr_dev->type == 3)
            timer_on_auto(&ncr_dev->timer, 10.0);
    }
}

static uint8_t
t128_read(uint32_t addr, void *priv)
{
    ncr5380_t     *ncr_dev = (ncr5380_t *) priv;
    const ncr_t   *ncr     = &ncr_dev->ncr;
    scsi_device_t *dev     = &scsi_devices[ncr_dev->bus][ncr->target_id];
    uint8_t        ret     = 0xff;

    addr &= 0x3fff;
    if (ncr_dev->t128.bios_enabled && (addr >= 0) && (addr < 0x1800))
        ret = ncr_dev->bios_rom.rom[addr & 0x1fff];
    else if ((addr >= 0x1800) && (addr < 0x1880))
        ret = ncr_dev->t128.ext_ram[addr & 0x7f];
    else if ((addr >= 0x1c00) && (addr < 0x1c20)) {
        ret = ncr_dev->t128.ctrl;
        ncr_log("T128 ctrl read=%02x, dma=%02x\n", ret, ncr->mode & MODE_DMA);
    } else if ((addr >= 0x1c20) && (addr < 0x1c40)) {
        ret = ncr_dev->t128.status;
        ncr_log("T128 status read=%02x, dma=%02x\n", ret, ncr->mode & MODE_DMA);
    } else if ((addr >= 0x1d00) && (addr < 0x1e00))
        ret = ncr_read((addr - 0x1d00) >> 5, ncr_dev);
    else if (addr >= 0x1e00 && addr < 0x2000) {
        if ((ncr_dev->t128.host_pos >= MIN(512, dev->buffer_length)) ||
            (ncr->dma_mode != DMA_INITIATOR_RECEIVE))
            ret = 0xff;
        else {
            ret = ncr_dev->t128.buffer[ncr_dev->t128.host_pos++];

            ncr_log("Read transfer, addr = %i, pos = %i\n", addr & 0x1ff,
                    ncr_dev->t128.host_pos);

            if (ncr_dev->t128.host_pos == MIN(512, dev->buffer_length)) {
                ncr_dev->t128.status &= ~0x04;
                ncr_log("Transfer busy read, status = %02x, period = %lf\n",
                        ncr_dev->t128.status, ncr_dev->period);
                if ((ncr_dev->period == 0.2) || (ncr_dev->period == 0.02))
                    timer_on_auto(&ncr_dev->timer, 40.2);
            } else if ((ncr_dev->t128.host_pos < MIN(512, dev->buffer_length)) &&
                       (scsi_device_get_callback(dev) > 100.0))
                cycles += 100; /*Needed to avoid timer de-syncing with transfers.*/
        }
    }

    return ret;
}

static void
t128_write(uint32_t addr, uint8_t val, void *priv)
{
    ncr5380_t           *ncr_dev = (ncr5380_t *) priv;
    const ncr_t         *ncr     = &ncr_dev->ncr;
    const scsi_device_t *dev     = &scsi_devices[ncr_dev->bus][ncr->target_id];

    addr &= 0x3fff;
    if ((addr >= 0x1800) && (addr < 0x1880))
        ncr_dev->t128.ext_ram[addr & 0x7f] = val;
    else if ((addr >= 0x1c00) && (addr < 0x1c20)) {
        if ((val & 0x02) && !(ncr_dev->t128.ctrl & 0x02))
            ncr_dev->t128.status |= 0x02;

        ncr_dev->t128.ctrl = val;
        ncr_log("T128 ctrl write=%02x\n", val);
    } else if ((addr >= 0x1d00) && (addr < 0x1e00))
        ncr_write((addr - 0x1d00) >> 5, val, ncr_dev);
    else if ((addr >= 0x1e00) && (addr < 0x2000)) {
        if ((ncr_dev->t128.host_pos < MIN(512, dev->buffer_length)) &&
            (ncr->dma_mode == DMA_SEND)) {
            ncr_dev->t128.buffer[ncr_dev->t128.host_pos] = val;
            ncr_dev->t128.host_pos++;

            ncr_log("Write transfer, addr = %i, pos = %i, val = %02x\n",
                    addr & 0x1ff, ncr_dev->t128.host_pos, val);

            if (ncr_dev->t128.host_pos == MIN(512, dev->buffer_length)) {
                ncr_dev->t128.status &= ~0x04;
                ncr_dev->ncr_busy = 1;
                ncr_log("Transfer busy write, status = %02x\n", ncr_dev->t128.status);
                timer_on_auto(&ncr_dev->timer, 0.02);
            }
        }
    }
}

static uint8_t
rt1000b_mc_read(int port, void *priv)
{
    const ncr5380_t *ncr_dev = (ncr5380_t *) priv;

    return (ncr_dev->pos_regs[port & 7]);
}

static void
rt1000b_mc_write(int port, uint8_t val, void *priv)
{
    ncr5380_t *ncr_dev = (ncr5380_t *) priv;

    /* MCA does not write registers below 0x0100. */
    if (port < 0x0102)
        return;

    mem_mapping_disable(&ncr_dev->bios_rom.mapping);
    mem_mapping_disable(&ncr_dev->mapping);

    /* Save the MCA register value. */
    ncr_dev->pos_regs[port & 7] = val;

    if (ncr_dev->pos_regs[2] & 1) {
        switch (ncr_dev->pos_regs[2] & 0xe0) {
            case 0:
                ncr_dev->rom_addr = 0xd4000;
                break;
            case 0x20:
                ncr_dev->rom_addr = 0xd0000;
                break;
            case 0x40:
                ncr_dev->rom_addr = 0xcc000;
                break;
            case 0x60:
                ncr_dev->rom_addr = 0xc8000;
                break;
            case 0xc0:
                ncr_dev->rom_addr = 0xdc000;
                break;
            case 0xe0:
                ncr_dev->rom_addr = 0xd8000;
                break;

            default:
                break;
        }

        mem_mapping_set_addr(&ncr_dev->bios_rom.mapping, ncr_dev->rom_addr, 0x4000);
        mem_mapping_set_addr(&ncr_dev->mapping, ncr_dev->rom_addr, 0x4000);
    }
}

static uint8_t
rt1000b_mc_feedb(void *priv)
{
    const ncr5380_t *ncr_dev = (ncr5380_t *) priv;

    return ncr_dev->pos_regs[2] & 1;
}

static void *
ncr_init(const device_t *info)
{
    const char *fn = NULL;
    char        temp[128];
    ncr5380_t  *ncr_dev;

    ncr_dev = malloc(sizeof(ncr5380_t));
    memset(ncr_dev, 0x00, sizeof(ncr5380_t));
    ncr_dev->name = info->name;
    ncr_dev->type = info->local;

    ncr_dev->bus = scsi_get_bus();

    switch (ncr_dev->type) {
        case 0: /* Longshine LCS6821N */
            ncr_dev->rom_addr = device_get_config_hex20("bios_addr");
            ncr_dev->irq      = device_get_config_int("irq");
            rom_init(&ncr_dev->bios_rom, LCS6821N_ROM,
                     ncr_dev->rom_addr, 0x4000, 0x3fff, 0, MEM_MAPPING_EXTERNAL);

            mem_mapping_add(&ncr_dev->mapping, ncr_dev->rom_addr, 0x4000,
                            memio_read, NULL, NULL,
                            memio_write, NULL, NULL,
                            ncr_dev->bios_rom.rom, MEM_MAPPING_EXTERNAL, ncr_dev);
            break;

        case 1: /* Rancho RT1000B/MC */
            ncr_dev->rom_addr = device_get_config_hex20("bios_addr");
            ncr_dev->irq      = device_get_config_int("irq");
            ncr_dev->bios_ver = device_get_config_int("bios_ver");
            if (info->flags & DEVICE_MCA) {
                ncr_dev->rom_addr = 0xd8000;
                ncr_dev->bios_ver = 1;
            }

            switch (ncr_dev->bios_ver) {
                case 0:
                    fn = RT1000B_810R_ROM;
                    break;
                case 1:
                    fn = RT1000B_820R_ROM;
                    break;
            }

            rom_init(&ncr_dev->bios_rom, fn,
                     ncr_dev->rom_addr, 0x4000, 0x3fff, 0, MEM_MAPPING_EXTERNAL);

            if (info->flags & DEVICE_MCA) {
                mem_mapping_add(&ncr_dev->mapping, 0, 0,
                                memio_read, NULL, NULL,
                                memio_write, NULL, NULL,
                                ncr_dev->bios_rom.rom, MEM_MAPPING_EXTERNAL, ncr_dev);
                ncr_dev->pos_regs[0] = 0x8d;
                ncr_dev->pos_regs[1] = 0x70;
                mca_add(rt1000b_mc_read, rt1000b_mc_write, rt1000b_mc_feedb, NULL, ncr_dev);
            } else {
                mem_mapping_add(&ncr_dev->mapping, ncr_dev->rom_addr, 0x4000,
                                memio_read, NULL, NULL,
                                memio_write, NULL, NULL,
                                ncr_dev->bios_rom.rom, MEM_MAPPING_EXTERNAL, ncr_dev);
            }
            break;

        case 2: /* Trantor T130B */
            ncr_dev->rom_addr = device_get_config_hex20("bios_addr");
            ncr_dev->base     = device_get_config_hex16("base");
            ncr_dev->irq      = device_get_config_int("irq");

            if (ncr_dev->rom_addr > 0x00000) {
                rom_init(&ncr_dev->bios_rom, T130B_ROM,
                         ncr_dev->rom_addr, 0x4000, 0x3fff, 0, MEM_MAPPING_EXTERNAL);

                mem_mapping_add(&ncr_dev->mapping, ncr_dev->rom_addr, 0x4000,
                                t130b_read, NULL, NULL,
                                t130b_write, NULL, NULL,
                                ncr_dev->bios_rom.rom, MEM_MAPPING_EXTERNAL, ncr_dev);
            }

            io_sethandler(ncr_dev->base, 16,
                          t130b_in, NULL, NULL, t130b_out, NULL, NULL, ncr_dev);
            break;

        case 3: /* Trantor T128 */
            ncr_dev->rom_addr          = device_get_config_hex20("bios_addr");
            ncr_dev->irq               = device_get_config_int("irq");
            ncr_dev->t128.bios_enabled = device_get_config_int("boot");

            if (ncr_dev->t128.bios_enabled)
                rom_init(&ncr_dev->bios_rom, T128_ROM,
                         ncr_dev->rom_addr, 0x4000, 0x3fff, 0, MEM_MAPPING_EXTERNAL);

            mem_mapping_add(&ncr_dev->mapping, ncr_dev->rom_addr, 0x4000,
                            t128_read, NULL, NULL,
                            t128_write, NULL, NULL,
                            ncr_dev->bios_rom.rom, MEM_MAPPING_EXTERNAL, ncr_dev);
            break;

        case 4: /* Corel LS2000 */
            ncr_dev->rom_addr = device_get_config_hex20("bios_addr");
            ncr_dev->irq      = device_get_config_int("irq");
            rom_init(&ncr_dev->bios_rom, COREL_LS2000_ROM,
                     ncr_dev->rom_addr, 0x4000, 0x3fff, 0, MEM_MAPPING_EXTERNAL);

            mem_mapping_add(&ncr_dev->mapping, ncr_dev->rom_addr, 0x4000,
                            memio_read, NULL, NULL,
                            memio_write, NULL, NULL,
                            ncr_dev->bios_rom.rom, MEM_MAPPING_EXTERNAL, ncr_dev);
            break;

        default:
            break;
    }

    sprintf(temp, "%s: BIOS=%05X", ncr_dev->name, ncr_dev->rom_addr);
    if (ncr_dev->base != 0)
        sprintf(&temp[strlen(temp)], " I/O=%04x", ncr_dev->base);
    if (ncr_dev->irq != 0)
        sprintf(&temp[strlen(temp)], " IRQ=%d", ncr_dev->irq);
    ncr_log("%s\n", temp);

    if ((ncr_dev->type < 3) || (ncr_dev->type == 4)) {
        ncr_dev->status_ctrl       = STATUS_BUFFER_NOT_READY;
        ncr_dev->buffer_host_pos   = 128;
    } else {
        ncr_dev->t128.status       = 0x04;
        ncr_dev->t128.host_pos     = 512;
        if (!ncr_dev->t128.bios_enabled)
            ncr_dev->t128.status |= 0x80;
    }
    timer_add(&ncr_dev->timer, ncr_callback, ncr_dev, 0);

    scsi_bus_set_speed(ncr_dev->bus, 5000000.0);

    return ncr_dev;
}

static void
ncr_close(void *priv)
{
    ncr5380_t *ncr_dev = (ncr5380_t *) priv;

    if (ncr_dev) {
        /* Tell the timer to terminate. */
        timer_stop(&ncr_dev->timer);

        free(ncr_dev);
        ncr_dev = NULL;
    }
}

static int
lcs6821n_available(void)
{
    return (rom_present(LCS6821N_ROM));
}

static int
rt1000b_available(void)
{
    return (rom_present(RT1000B_820R_ROM) && rom_present(RT1000B_810R_ROM));
}

static int
rt1000b_820_available(void)
{
    return (rom_present(RT1000B_820R_ROM));
}

static int
t130b_available(void)
{
    return (rom_present(T130B_ROM));
}

static int
t128_available(void)
{
    return (rom_present(T128_ROM));
}

static int
corel_ls2000_available(void)
{
    return (rom_present(COREL_LS2000_ROM));
}

// clang-format off
static const device_config_t ncr5380_mmio_config[] = {
    {
        .name = "bios_addr",
        .description = "BIOS Address",
        .type = CONFIG_HEX20,
        .default_string = "",
        .default_int = 0xD8000,
        .file_filter = "",
        .spinner = { 0 },
        .selection = {
            { .description = "C800H", .value = 0xc8000 },
            { .description = "CC00H", .value = 0xcc000 },
            { .description = "D000H", .value = 0xd0000 },
            { .description = "D400H", .value = 0xd4000 },
            { .description = "D800H", .value = 0xd8000 },
            { .description = "DC00H", .value = 0xdc000 },
            { .description = ""                        }
        },
    },
    {
        .name = "irq",
        .description = "IRQ",
        .type = CONFIG_SELECTION,
        .default_string = "",
        .default_int = 5,
        .file_filter = "",
        .spinner = { 0 },
        .selection = {
            { .description = "IRQ 3", .value = 3 },
            { .description = "IRQ 5", .value = 5 },
            { .description = "IRQ 7", .value = 7 },
            { .description = ""                  }
        },
    },
    { .name = "", .description = "", .type = CONFIG_END }
};

static const device_config_t rancho_config[] = {
    {
        .name = "bios_addr",
        .description = "BIOS Address",
        .type = CONFIG_HEX20,
        .default_string = "",
        .default_int = 0xD8000,
        .file_filter = "",
        .spinner = { 0 },
        .selection = {
            { .description = "C800H", .value = 0xc8000 },
            { .description = "CC00H", .value = 0xcc000 },
            { .description = "D000H", .value = 0xd0000 },
            { .description = "D400H", .value = 0xd4000 },
            { .description = "D800H", .value = 0xd8000 },
            { .description = "DC00H", .value = 0xdc000 },
            { .description = ""                        }
        },
    },
    {
        .name = "irq",
        .description = "IRQ",
        .type = CONFIG_SELECTION,
        .default_string = "",
        .default_int = 5,
        .file_filter = "",
        .spinner = { 0 },
        .selection = {
            { .description = "IRQ 3", .value = 3 },
            { .description = "IRQ 5", .value = 5 },
            { .description = "IRQ 7", .value = 7 },
            { .description = ""                  }
        },
    },
    {
        .name = "bios_ver",
        .description = "BIOS Version",
        .type = CONFIG_SELECTION,
        .default_string = "",
        .default_int = 1,
        .file_filter = "",
        .spinner = { 0 },
        .selection = {
            { .description = "8.20R", .value = 1 },
            { .description = "8.10R", .value = 0 },
            { .description = ""                  }
        },
    },
    { .name = "", .description = "", .type = CONFIG_END }
};

static const device_config_t rancho_mc_config[] = {
    {
        .name = "irq",
        .description = "IRQ",
        .type = CONFIG_SELECTION,
        .default_string = "",
        .default_int = 5,
        .file_filter = "",
        .spinner = { 0 },
        .selection = {
            { .description = "IRQ 3", .value = 3 },
            { .description = "IRQ 5", .value = 5 },
            { .description = "IRQ 7", .value = 7 },
            { .description = ""                  }
        },
    },
    { .name = "", .description = "", .type = CONFIG_END }
};

static const device_config_t t130b_config[] = {
    {
        .name = "bios_addr",
        .description = "BIOS Address",
        .type = CONFIG_HEX20,
        .default_string = "",
        .default_int = 0xD8000,
        .file_filter = "",
        .spinner = { 0 },
        .selection = {
            { .description = "Disabled", .value =       0 },
            { .description = "C800H",    .value = 0xc8000 },
            { .description = "CC00H",    .value = 0xcc000 },
            { .description = "D800H",    .value = 0xd8000 },
            { .description = "DC00H",    .value = 0xdc000 },
            { .description = ""                           }
        },
    },
    {
        .name = "base",
        .description = "Address",
        .type = CONFIG_HEX16,
        .default_string = "",
        .default_int = 0x0350,
        .file_filter = "",
        .spinner = { 0 },
        .selection = {
            { .description = "240H", .value = 0x0240 },
            { .description = "250H", .value = 0x0250 },
            { .description = "340H", .value = 0x0340 },
            { .description = "350H", .value = 0x0350 },
            { .description = ""                      }
        },
    },
    {
        .name = "irq",
        .description = "IRQ",
        .type = CONFIG_SELECTION,
        .default_string = "",
        .default_int = 5,
        .file_filter = "",
        .spinner = { 0 },
        .selection = {
            { .description = "IRQ 3", .value = 3 },
            { .description = "IRQ 5", .value = 5 },
            { .description = "IRQ 7", .value = 7 },
            { .description = ""                  }
        },
    },
    { .name = "", .description = "", .type = CONFIG_END }
};

static const device_config_t t128_config[] = {
    {
        .name = "bios_addr",
        .description = "BIOS Address",
        .type = CONFIG_HEX20,
        .default_string = "",
        .default_int = 0xD8000,
        .file_filter = "",
        .spinner = { 0 },
        .selection = {
            { .description = "C800H", .value = 0xc8000 },
            { .description = "CC00H", .value = 0xcc000 },
            { .description = "D000H", .value = 0xd0000 },
            { .description = "D400H", .value = 0xd4000 },
            { .description = "D800H", .value = 0xd8000 },
            { .description = "DC00H", .value = 0xdc000 },
            { .description = ""                        }
        },
    },
    {
        .name = "irq",
        .description = "IRQ",
        .type = CONFIG_SELECTION,
        .default_string = "",
        .default_int = 5,
        .file_filter = "",
        .spinner ={ 0 },
        .selection = {
            { .description = "IRQ 3", .value = 3 },
            { .description = "IRQ 5", .value = 5 },
            { .description = "IRQ 7", .value = 7 },
            { .description = ""                  }
        },
    },
    {
        .name = "boot",
        .description = "Enable Boot ROM",
        .type = CONFIG_BINARY,
        .default_string = "",
        .default_int = 1
    },
    { .name = "", .description = "", .type = CONFIG_END }
};
// clang-format on

const device_t scsi_lcs6821n_device = {
    .name          = "Longshine LCS-6821N",
    .internal_name = "lcs6821n",
    .flags         = DEVICE_ISA,
    .local         = 0,
    .init          = ncr_init,
    .close         = ncr_close,
    .reset         = NULL,
    { .available = lcs6821n_available },
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = ncr5380_mmio_config
};

const device_t scsi_rt1000b_device = {
    .name          = "Rancho RT1000B",
    .internal_name = "rt1000b",
    .flags         = DEVICE_ISA,
    .local         = 1,
    .init          = ncr_init,
    .close         = ncr_close,
    .reset         = NULL,
    { .available = rt1000b_available },
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = rancho_config
};

const device_t scsi_rt1000mc_device = {
    .name          = "Rancho RT1000B-MC",
    .internal_name = "rt1000mc",
    .flags         = DEVICE_MCA,
    .local         = 1,
    .init          = ncr_init,
    .close         = ncr_close,
    .reset         = NULL,
    { .available = rt1000b_820_available },
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = rancho_mc_config
};

const device_t scsi_t130b_device = {
    .name          = "Trantor T130B",
    .internal_name = "t130b",
    .flags         = DEVICE_ISA,
    .local         = 2,
    .init          = ncr_init,
    .close         = ncr_close,
    .reset         = NULL,
    { .available = t130b_available },
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = t130b_config
};

const device_t scsi_t128_device = {
    .name          = "Trantor T128",
    .internal_name = "t128",
    .flags         = DEVICE_ISA,
    .local         = 3,
    .init          = ncr_init,
    .close         = ncr_close,
    .reset         = NULL,
    { .available = t128_available },
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = t128_config
};

const device_t scsi_ls2000_device = {
    .name          = "Corel LS2000",
    .internal_name = "ls2000",
    .flags         = DEVICE_ISA,
    .local         = 4,
    .init          = ncr_init,
    .close         = ncr_close,
    .reset         = NULL,
    { .available = corel_ls2000_available },
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = ncr5380_mmio_config
};
